#include "bg_element.h"
#include "bg_scene.h"
#include "bg_biped.h"
#include "bg_decal.h"
#include "bg_partsys.h"

#include "num/rand.h"
#include "script/scripthelper.h"
#include "script/write_lua.h"
#include "graphics/graphics.h"
#include "graphics/graphics_util.h"
#include "dd/dd_man.h"


#include "util/timer.h"				// log
#include "util/log.h"				// log
#include "util/common_macros.h"		// macros
#include "util/mmgr.h"		    

#include <math.h>
#include <cassert>
#include <algorithm>

using namespace background;
using namespace script;

BGElement::BGElement(BGScene*	pScene) :
	m_Sort(0),
	m_pScene(pScene),
	m_Rotation(0),
	m_InitialSort(0),
	m_InitialRotation(0),
	m_Type(ELEMENT_UNKNOWN),
	m_IsActive(true),
	m_WasLoadedFromFile(false)
{
	m_ClipSize = math::Vec2(32/(float)GraphicsUtil::WIDTH, 32/(float)GraphicsUtil::HEIGHT);
}

BGElement::~BGElement()
{

}

math::Vec2	BGElement::GetScreenPos(const math::Vec2& Offset, float Scale)
{
	math::Vec2	Pos = m_Pos - Offset;

	if (m_pScene->IsWrapping())
	{
		const math::Vec2& Max = m_pScene->GetMax();
		while (Pos.x > Max.x-1) 	Pos.x -= Max.x;
		while (Pos.y > Max.y-1) 	Pos.y -= Max.y;
		while (Pos.x < -1) 	Pos.x += Max.x;
		while (Pos.y < -1) 	Pos.y += Max.y;
	}

	math::Vec2	P = (Pos + (m_Size*0.5f))*Scale;
	return P;
}

void	BGElement::RenderDebug(const math::Vec2& Offset, float Scale)
{
	math::Vec2	P = GetScreenPos(Offset, Scale);
	dd::Manager::Instance().AddBox2D(P-(m_Size*Scale*0.5f), P + (m_Size*Scale*0.5f), ColorRGBA(255, 0, 0, 255));
}

bool	BGElement::IsVisible(const math::Vec2&	Offset, float Scale)
{
	math::Vec2	P = GetScreenPos(Offset, Scale);
		
	if (P.x - (m_ClipSize.x*Scale) > 1.05f) return false;
	if (P.y - (m_ClipSize.y*Scale) > 1.05f) return false;

	if (P.x + (m_ClipSize.x*Scale) < -0.05f ) return false;
	if (P.y + (m_ClipSize.y*Scale) < -0.05f) return false;
	return true;
}

void	BGElement::Pan(float dt)
{
	m_Pos += m_Dir * dt;

	math::Vec2 	Min = m_pScene->GetOffset() - math::Vec2(0.0f, 0.0f);
	math::Vec2	Max = m_pScene->GetOffset() + math::Vec2(1.0f, 1.0f);
	if (!IsVisible(m_pScene->GetOffset(), m_pScene->GetScale()))
	{
		if (fabs(m_Dir.x) > fabs(m_Dir.y))
		{
			if (m_Dir.x > 0)	m_Pos.x = Min.x-m_Size.x; else
								m_Pos.x = Max.x; 
			m_Pos.y = m_BasePos.y;
		} else
		{
			if (m_Dir.y > 0)	m_Pos.y = Max.y-m_Size.y; else
								m_Pos.y = Max.y; 
			m_Pos.x = m_BasePos.x;
		}
	}
}

void	BGElement::Rotate(float dt)
{
	const float BASE_SPEED = 300;
	m_Rotation+=(m_Dir.x + m_Dir.y)* dt * BASE_SPEED;
}

void	BGElement::ResetElement()
{
	if (!m_WasLoadedFromFile) return; // don't reset as the initial variables were never stored

	m_Pos = m_InitialPos;
	m_Size = m_InitialSize;
	m_Dir = m_InitialDir;
	m_BasePos = m_InitialBasePos;
	m_Sort = m_InitialSort;
	m_Rotation = m_InitialRotation;

	OnElementReset();
}

void	BGElement::Tick(float dt)
{
}

void	BGElement::RecalcBoundingBox() 
{
	m_ClipSize.x = m_ClipSize.y = UTIL_HIGH(m_Size.x, m_Size.y) * 0.5f; 
}

bool	BGElement::InsideRegion(const math::Vec2& From, const math::Vec2& To)
{	
	math::Vec2	Max = m_Pos + m_Size;
	if (m_Pos.x > To.x || m_Pos.y > To.y || Max.x < From.x || Max.y < From.y) return false;
	return true;
}

bool	BGElement::IsInside(const math::Vec2& Pos, const math::Vec2& Offset)
{
	math::Vec2	P = m_Pos;
	return	UTIL_RANGE(Pos.x, P.x, P.x+m_Size.x) && 
			UTIL_RANGE(Pos.y, P.y, P.y+m_Size.y);
}

void	BGElement::Load(LuaPlus::LuaObject& Obj)
{
	m_Pos	= m_pScene->ConvertFromFile(ScriptHelper::GetTableVec2(Obj, "Pos"));
	m_Size	= m_pScene->ConvertFromFile(ScriptHelper::GetTableVec2(Obj, "Size", m_Size));
	m_Dir	= m_pScene->ConvertFromFile(ScriptHelper::GetTableVec2(Obj, "Dir"));
	m_Sort	= ScriptHelper::GetTableFloat(Obj, "Sort");
	m_Rotation	= ScriptHelper::GetTableFloat(Obj, "Rotation");
	m_BasePos = m_Pos;

	m_InitialPos = m_Pos;
	m_InitialSize = m_Size;
	m_InitialDir = m_Dir;
	m_InitialBasePos = m_BasePos;
	m_InitialSort = m_Sort;
	m_InitialRotation = m_Rotation;

	m_WasLoadedFromFile = true;
}

void	BGElement::Save(script::WriteLua* pWL)
{
	pWL->WritePair("Type",	GetType());
	pWL->WritePair("Pos",	m_Pos);
	pWL->WritePair("Size",	m_Size);
	pWL->WritePair("Dir",	m_Dir);
	pWL->WritePair("Sort",	m_Sort);
	pWL->WritePair("Rotation",	m_Rotation);
}

void	BGElement::CopyData(BGElement*	pFrom)
{
	m_Pos = pFrom->m_Pos;
	m_Size = m_Size;
	m_Dir = pFrom->m_Dir;
	m_Sort = pFrom->m_Sort;
	m_Rotation = pFrom->m_Rotation;

	m_InitialPos = pFrom->m_InitialPos;
	m_InitialSize = pFrom->m_InitialSize;
	m_InitialDir = pFrom->m_InitialDir;
	m_InitialBasePos = pFrom->m_InitialBasePos;
	m_InitialSort = pFrom->m_InitialSort;
	m_InitialRotation = pFrom->m_InitialRotation;

	m_WasLoadedFromFile = pFrom->m_WasLoadedFromFile;
}

BGElement*		BGElement::Clone()
{
	BGElement*	pElement = FromName(m_pScene, m_Name);
	if (!pElement) return NULL;
	pElement->CopyData(this);
	return pElement;
}

BGElement::ELEMENT_TYPE BGElement::MapStringToType(const std::string& Name)
{
	for (unsigned int	i=0; i<ELEMENT_C; i++)
	{
		ELEMENT_TYPE c=ELEMENT_TYPE(i);

		if (strcmpi(Name.c_str(), MapTypeToString(c).c_str())==0)
			return c;
		
	}
	return ELEMENT_UNKNOWN;
}

std::string		BGElement::MapTypeToString(BGElement::ELEMENT_TYPE t)
{
	switch(t)
	{
		case ELEMENT_PARTSYS:	return "partsys";
		case ELEMENT_DECAL:		return "decal";
		case ELEMENT_UVDECAL:	return "uvdecal";
		case ELEMENT_BOBDECAL:	return "bobdecal";
		case ELEMENT_ROTDECAL:	return "rotdecal";
		case ELEMENT_PANDECAL:	return "pandecal";
		case ELEMENT_GLOWDECAL: return "glowdecal";
		case ELEMENT_BIPED:		return "biped";
	}
	return "unknown";
}
	
BGElement*	BGElement::FromName(BGScene*	pScene, const std::string& Name)
{
	BGElement::ELEMENT_TYPE t = MapStringToType(Name);
	switch(t)
	{
		case ELEMENT_PARTSYS:	return new BGPartSys(pScene); 
		case ELEMENT_DECAL:		return new BGDecal(pScene); 
		case ELEMENT_UVDECAL:	return new BGUVDecal(pScene); 
		case ELEMENT_BOBDECAL:	return new BGBobDecal(pScene);
		case ELEMENT_ROTDECAL:	return new BGRotDecal(pScene);
		case ELEMENT_GLOWDECAL: return new BGGlowDecal(pScene);
		case ELEMENT_PANDECAL:	return new BGPanDecal(pScene);
		case ELEMENT_BIPED:		return new BGBiped(pScene); 
	}
	return pScene->GetCustomElement(Name);
}

BGElement*	BGElement::LoadElement(BGScene*	pScene, LuaPlus::LuaObject&	Obj)
{
	if (!Obj.IsTable()) return NULL;

	std::string		Type = ScriptHelper::GetTableString(Obj, "Type", "");
	if (Type.empty()) return NULL;

	BGElement*	pElement = FromName(pScene, Type);

	// finalize the load
	if (!pElement) return NULL;
	pElement->Load(Obj);
	return pElement;
}

